﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.ComponentModel;

namespace ServerSocketTest
{
    public static class SocketCommandToken
    {
        //left Quotation Mark
        private const string lQuote = "<<";
        //right Quotation Mark
        private const string rQuote = ">>";
        // below token, please put it in beginning of data package
        public static String CloseConnection { get { return lQuote + "CLOSE" + rQuote; } } 


        // below token, please put it in end of data package
        public static String DataEOF { get { return lQuote + "EOF" + rQuote; } } 
    }

    public enum SocketCmdToken
    {
        _cmdSTART_,
        _cmdCLOSE_,
        _cmdEOF_
    }

    /// <summary>
    /// ref example from http://msdn.microsoft.com/zh-tw/library/fx6588te(v=vs.110).aspx
    /// </summary>
    public class ServerSocket : IDisposable
    {
        private List<byte[]> gSocketCmdTokenBArray;


        public delegate void InformationLoger(string inform);
        InformationLoger SocketInformLoger;

        //// Declare a delegate that takes a single string parameter
        //// and has no return type.
        //public delegate void LogHandler(string message);

        //public static int ConnectionPort = 443;

        //默認信號為未發送狀態
        private static ManualResetEvent SignalEvent = new ManualResetEvent(false);

        private BackgroundWorker bgwServer;
        private IPEndPoint gLocalEndPoint;
        private Socket gListenerSocket;

        //參數會指定可在佇列中等候接收的輸入連接數。若要決定可指定的最大連接數，除非同時間的連線非常的大，否則值4應該很夠用。
        private int gMaxListenIncomingConnections;

        private bool gListenerAcceptLoopFlag = false;

        private List<Socket> gClientSocketList;
        private int clientNo;



        //public ServerSocket()
        //{
        //    //建立本機監聽位址及埠號，IPAddress.Any表示監聽所有的介面。
        //    gLocalEndPoint = new IPEndPoint(IPAddress.Any, ConnectionPort);
        //    initServerSocket();
        //}
        public ServerSocket(int connectPortNumber, int maxListenIncomingConnections)
        {
            //建立本機監聽位址及埠號，IPAddress.Any表示監聽所有的介面。
            gLocalEndPoint = new IPEndPoint(IPAddress.Any, connectPortNumber);
            gMaxListenIncomingConnections = maxListenIncomingConnections;
            initServerSocket();
        }
        public ServerSocket(string ip, int connectPortNumber, int maxListenIncomingConnections)
        {
            //建立本機監聽位址及埠號，IPAddress.Any表示監聽所有的介面。
            gLocalEndPoint = new IPEndPoint(IPAddress.Parse(ip), connectPortNumber);
            gMaxListenIncomingConnections = maxListenIncomingConnections;
            initServerSocket();
        }
        public ServerSocket(IPEndPoint localEndPoint, int maxListenIncomingConnections)
        {
            //建立本機監聽位址及埠號，IPAddress.Any表示監聽所有的介面。
            gLocalEndPoint = localEndPoint;
            gMaxListenIncomingConnections = maxListenIncomingConnections;
            initServerSocket();

        }
        private void initServerSocket()
        {
            string[] tokenName = Enum.GetNames(typeof(SocketCmdToken));
            gSocketCmdTokenBArray = new List<byte[]>();
            foreach (string s in tokenName)
            {
                byte[] byteArray = System.Text.Encoding.UTF8.GetBytes(s);
                //SocketInformLog("byte token = " + s + Environment.NewLine);
                gSocketCmdTokenBArray.Add(byteArray);
            }

            //byte[] a = gSocketCmdTokenBArray[1];
            //// 實體化 ListenerSocket
            //// Create a TCP/IP socket.
            //gListenerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //SocketInformLog("Server is Created : " + gLocalEndPoint.Address + ", Port: " + gLocalEndPoint.Port + Environment.NewLine);
            
            ////使用Thread
            ////new Thread(() => StartListening()).Start();

            clientNo = 0;
            gClientSocketList = new List<Socket>();
            //queMessage = new Queue<string>();
            bgwServer = new BackgroundWorker();
            bgwServer.WorkerReportsProgress = true;
            bgwServer.WorkerSupportsCancellation = true;
            bgwServer.DoWork += new DoWorkEventHandler(bgwServerDoWork);
            bgwServer.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgwServerRunWorkerCompleted);
        }
        /// <summary>
        /// Implement IDisposable.
        /// </summary>
        public void Dispose()
        {
            try
            {
                gListenerAcceptLoopFlag = false;
                //gListenerSocket.Shutdown(SocketShutdown.Both);
                gListenerSocket.Close();
            }
            catch
            {
            }
            //GC.SuppressFinalize(this);
        }
        public void Start()
        {
            if (!bgwServer.IsBusy)
            {
                bgwServer.RunWorkerAsync();
            }
            else
            {
                SocketInformLog("Server was already started Listening." + Environment.NewLine);
            }
        }
        public void Stop()
        {
            gListenerAcceptLoopFlag = false;
            //SignalEvent.Set();
            //bgwServer.CancelAsync();
            foreach (Socket s in gClientSocketList)
            {
                try
                {
                    SocketInformLog("Client is going to be disconnected. ip : " + s.RemoteEndPoint.ToString().Split(':')[0] + ",  Client Socket HashCode = " + s.GetHashCode() + Environment.NewLine);
                    closeClientSocket(s, false, true);
                }
                catch (Exception e)
                {
                    SocketInformLog("[Exception] Client being disconnected Failure. ip : " + s.RemoteEndPoint.ToString().Split(':')[0] + ",  Client Socket HashCode = " + s.GetHashCode() + e + Environment.NewLine);
                }
            }
            gClientSocketList.Clear();

            //gListenerSocket.Disconnect(false);
            //gListenerSocket.Shutdown(SocketShutdown.Both);
            gListenerSocket.Close();
        }
        private void closeClientSocket(Socket clientHandler, bool removeFromList, bool closeMsgSendOut)
        {
            if (removeFromList)
            {
                gClientSocketList.Remove(clientHandler);
            }
            if (closeMsgSendOut)
            {
                Send(clientHandler, "Server is going to close the connection.");
            }
            clientHandler.Disconnect(false);
            clientHandler.Shutdown(SocketShutdown.Both);
            clientHandler.Close();
        }
        private void bgwServerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            //這裡是程式完成時會觸發的事件，
            //這是 background執行緒結束後會觸發的值
            SocketInformLog("bgwServerRunWorkerCompleted." + Environment.NewLine);
        }
        private void bgwServerDoWork(object sender, DoWorkEventArgs e)
        {
            StartListening();
            SocketInformLog("Server has been stoped Listening." + Environment.NewLine);
        }
        private void StartListening()
        {
            // 實體化 ListenerSocket
            // Create a TCP/IP socket.
            gListenerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            SocketInformLog("Server is Created : " + gLocalEndPoint.Address + ", Port: " + gLocalEndPoint.Port + Environment.NewLine);

            //使用Thread
            //new Thread(() => StartListening()).Start();

            // Bind socket to endpoint and listen for incoming connections.
            try
            {
                //socket連繫到該位址
                gListenerSocket.Bind(gLocalEndPoint);

                // 啟動監聽
                //backlog=4 參數會指定可在佇列中等候接收的輸入連接數。若要決定可指定的最大連接數，除非同時間的連線非常的大，否則值4應該很夠用。
                gListenerSocket.Listen(gMaxListenIncomingConnections);

                gListenerAcceptLoopFlag = true;


                while (gListenerAcceptLoopFlag)
                {
                    //ManualResetEvent必須要再呼叫Reset方法後才會處於未收到信號的狀態
                    // Set the event to nonsignaled state.
                    SignalEvent.Reset();

                    SocketInformLog("Server is starting listening and begining accept. ListenerSocket HashCode = " + gListenerSocket.GetHashCode() + Environment.NewLine);
                    // 以上完成了SERVER的Listening
                    // 之後BeginAccept()  開啟執行緒準備接收client的要求。
                    // 此處的BeginAccept 第一個參數就是當Socket一開始接收到Client的連線要求時，
                    // 立刻會呼叫delegate的AcceptCallback的方法，這個AcceptCallback名稱是我們自己取的
                    // Start an asynchronous socket to listen for connections.                                        
                    gListenerSocket.BeginAccept(new AsyncCallback(AcceptCallback), gListenerSocket);

                    //WaitOne的方法鎖定了執行緒，狀態也是WaitSleepJoin.
                    // Wait until a connection is made before continuing.
                    SignalEvent.WaitOne();
                }

            }
            catch (Exception e)
            {
                SocketInformLog("[Exception] To start listening and to accept an incoming connection attempt failure. ==> " + e + Environment.NewLine);

                //throw new Exception("To start listening and to accept an incoming connection attempt failure. ==> " + e);
                //log.Write("StartListening: " + e.Message);   //2013/8/22: Log exception message
                //LogHelper.WriteServerDataLog(LogHelper.MsgType.Exception, LogHelper.Action.ServerMessages, "-", "StartListening: " + e.Message);
            }
            //Console.WriteLine("\nPress ENTER to continue...");
            //Console.Read();
        }

        private void AcceptCallback(IAsyncResult ar)
        {
            try
            {
                //喚醒多個執行緒,讓進入Waitting回到Running狀態。
                // Signal the main thread to continue.
                SignalEvent.Set();

                //AsyncState 取得符合或包含非同步作業資訊的使用者定義的物件
                // Get the socket that handles the client request.
                Socket listener = (Socket)ar.AsyncState;  // 取得主要的socket, 其中包含使用者定義的物件及handles the client request
                //將主要正在 listening(正在連入的client Socket)轉交給另一個臨時的handlerSocket變數，並且結束接受此一客戶端的連線

                Socket handlerSocket = listener.EndAccept(ar);
                //取得遠端節點的EndPoint handler.RemoteEndPoint.ToString().Split(':')[0]

                //SocketInformLog("A client has connected. client ip : " + handlerSocket.RemoteEP.ToString().Split(':')[0] + ",  Client Socket HashCode = " + handlerSocket.GetHashCode() + Environment.NewLine);

                gClientSocketList.Add(handlerSocket);
                // Create the state object.
                StateObject state = new StateObject();
                state.workSocket = handlerSocket;

                // 假設已連線的客戶端要傳送資料時，所指定的回呼函式-ReadCallback，這也是C#中的delagate型別
                // 定義當Socket 接收到資料時要交給哪一個函數處理，這裡定義了BeginReceive()
                // 參數第一個為存放的位置，為byte[]，傳入的資料會置於此陣列中；第二參數為啟始位置、第三參數為一次接收的最大長度；
                // 第四為封包的旗標，例如標記為"廣播封包"之類，
                // 第五參數為開始接受資料時叫用的回呼函式，用AsyncCallback實體化指定ReadCallback為回呼函式，所以當客戶端將資料傳進來時，ReadCallback函式會被叫用；
                // 第六為狀態的參數，這個是用戶自訂，當回呼函式ReadCallback被叫用 時，此參數的值會被傳遞到ReadCallback，其中包含Socket，但是回呼函式仍會曉得是哪一個Socket在傳送資料。
                handlerSocket.BeginReceive(state.buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReadCallback), state);
            }
            catch (Exception e)
            {
                SocketInformLog("[Exception][AcceptCallback] An error occurred when attempting to access the socket or to receive data from a connected Socket. ==> " + e + Environment.NewLine);
                //throw new Exception("An error occurred when attempting to access the socket or to receive data from a connected Socket. ==> " + e);
                //log.Write("AcceptCallBack: " + e.Message);   //2013/8/22: Log exception message
                //LogHelper.WriteServerDataLog(LogHelper.MsgType.Exception, LogHelper.Action.ServerMessages,
                //        "-", "AcceptCallBack: " + e.Message);
            }
        }

        private void ReadCallback(IAsyncResult ar)
        {
            String content = String.Empty;

            //重新取回 state object
            // Retrieve the state object and the handler socket from the asynchronous state object.
            StateObject state = (StateObject)ar.AsyncState;
            Socket handler = state.workSocket;


            try
            {
                //SocketInformLog("Client is waitting and begining to receive data. client ip : " + handler.RemoteEndPoint.ToString().Split(':')[0] + ",  Client Socket HashCode = " + handler.GetHashCode() + Environment.NewLine);

                //叫用 EndReceive 完成指定的非同步接收作業, 參數asyn (ar) 識別要完成的非同步接收作業，並要從其中擷取最終結果。
                // Read data from the client socket. 
                int bytesRead = handler.EndReceive(ar);
                //SocketInformLog("ReadCallback; byteRead = " + bytesRead);

                if (bytesRead > 0)
                {
                    #region msdn code
                    // There  might be more data, so store the data received so far.
                    //state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));
                    state.sb.Append(Encoding.UTF8.GetString(state.buffer, 0, bytesRead));

                    // Check for end-of-file tag. If it is not there, read 
                    // more data.
                    content = state.sb.ToString();
                    SocketInformLog("Client is receiving data. client ip : " + handler.RemoteEndPoint.ToString().Split(':')[0] + ",  Client Socket HashCode = " + handler.GetHashCode() + Environment.NewLine);
                    SocketInformLog("ReadCallback; content = " + content + Environment.NewLine);

                    state.listBuffer.AddRange(state.buffer);

                    if (content.IndexOf("<EOF>") > -1)
                    //if(LEVUtilityLibrary.MassUtilities.ByteArrayIndexOf(SocketCommandToken.DataEOF)
                    {
                        // All the data has been read from the 
                        // client. Display it on the console.
                        //Console.WriteLine("Read {0} bytes from socket. \n Data : {1}", content.Length, content);
                        // Echo the data back to the client.
                        //Send(handler, content);

                        SocketInformLog("ReadCallback; Get <EOF> and Send back to client." + Environment.NewLine);
                        Send(handler, "get Data finish");
                        state.listBuffer.Clear();
                        //state.sb.Clear();
                    }
                    else
                    {
                        SocketInformLog("ReadCallback; Not all data received. Get more." + Environment.NewLine);
                        // Not all data received. Get more.
                    }
                    handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                    new AsyncCallback(ReadCallback), state);
                    #endregion

                    #region modify code
                    // All data received. Get next message. 
                    // Not all data received. Get more.
                    //handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);        //new

                    //// There  might be more data, so store the data received so far.
                    ////state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));
                    //state.sb.Append(Encoding.UTF8.GetString(state.buffer, 0, bytesRead));


                    //// Check for end-of-file tag. If it is not there, read more data.
                    //content = state.sb.ToString();

                    //////////////////////////////

                    //if (content.IndexOf("<EOF>") > -1)
                    //{
                    //    //// Reply to the client.
                    //    //Send(handler, resultData);

                    //    // All data received. Get next message. 
                    //    handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);        //new

                    //}
                    //else
                    //{
                    //    // Not all data received. Get more.
                    //    handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);
                    //}
                    #endregion
                }
            }
            catch (SocketException e)
            {
                //if (ex.ErrorCode == 10054 || ex.ErrorCode == 10053)
                //{
                //    log.Write(client_ip + " has left");
                //    LogHelper.WriteServerDataLog(LogHelper.MsgType.Normal, LogHelper.Action.ClientLefted,
                //            client_ip, "Client has left");
                //}
                //else
                //{
                //    //Console.WriteLine(ex);
                //    log.Write("ReadCallBack1: " + ex.Message);  //2013/8/22: Log exception message
                //    LogHelper.WriteServerDataLog(LogHelper.MsgType.Exception, LogHelper.Action.ServerMessages,
                //            "-", "ReadCallBack1: " + ex.Message);
                //}

                SocketInformLog("[SocketException][ReadCallback] An error occurred when attempting to access the socket or to receive data from a connected Socket. ==> " + e + Environment.NewLine);
                //throw new Exception("[SocketException] An error occurred when attempting to access the socket or to receive data from a connected Socket. ==> " + e);
                SocketInformLog("handlerSocket Close. HashCode = " + handler.GetHashCode() + Environment.NewLine);

                closeClientSocket(handler, true, false);
           }
            catch (Exception e)
            {
                SocketInformLog("[Exception][ReadCallback] An error occurred when attempting to access the socket or to receive data from a connected Socket. ==> " + e + Environment.NewLine);
                //throw new Exception("[Exception] An error occurred when attempting to access the socket or to receive data from a connected Socket. ==> " + e);
                //log.Write("ReadCallBack2: " + e.Message);   //2013/8/22: Log exception message
                //LogHelper.WriteServerDataLog(LogHelper.MsgType.Exception, LogHelper.Action.ServerMessages,
                //        "-", "ReadCallBack2: " + e.Message);
            }
        }

        public void Send(Socket handler, String data)
        {
            try
            {
                //log.Write(handler.RemoteEndPoint.ToString().Split(':')[0] + " >> " + data);
                //LogHelper.WriteServerDataLog(LogHelper.MsgType.Normal, LogHelper.Action.TransmittedToClient,
                //        handler.RemoteEndPoint.ToString().Split(':')[0], data);

                // Convert the string data to byte data using encoding.
                byte[] byteData = Encoding.UTF8.GetBytes(data);

                SocketInformLog("Send back to Client = " + data + Environment.NewLine);

                // Begin sending the data to the remote device.
                handler.BeginSend(byteData, 0, byteData.Length, SocketFlags.None, new AsyncCallback(SendCallback), handler);
            }
            catch (Exception e)
            {
                SocketInformLog("[Exception][Send] Sends data asynchronously to a connected Socket Failure. ==> " + e + Environment.NewLine);
                //throw new Exception("[Exception] Sends data asynchronously to a connected Socket Failure. ==> " + e);
                //log.Write("Send: " + e.Message);   //2013/8/22: Log exception message
                //LogHelper.WriteServerDataLog(LogHelper.MsgType.Exception, LogHelper.Action.ServerMessages,
                //        "-", "Send: " + e.Message);
            }
        }

        private void SendCallback(IAsyncResult ar)
        {
            try
            {
                // Retrieve the socket from the state object.
                Socket handler = (Socket)ar.AsyncState;
                #region msdn
                // Complete sending the data to the remote device.
                int bytesSent = handler.EndSend(ar);
                //Console.WriteLine("Sent {0} bytes to client.", bytesSent);
                //Thread.Sleep(10000);
                SocketInformLog("SendCallback;  bytesSent = " + bytesSent + Environment.NewLine);


                //handler.Shutdown(SocketShutdown.Both);
                //handler.Close();
                #endregion
                #region modify code
                // Complete sending the data to the remote device.
                //int bytesSent = handler.EndSend(ar);
                #endregion
            }
            catch (Exception e)
            {
                SocketInformLog("[Exception][SendCallback] Sends data asynchronously to a connected Socket Failure. ==> " + e + Environment.NewLine);
                //throw new Exception("[Exception] Sends data asynchronously to a connected Socket (SendCallback) Failure. ==> " + e);
                //log.Write("SendCallBack: " + e.Message);   //2013/8/22: Log exception message
                //LogHelper.WriteServerDataLog(LogHelper.MsgType.Exception, LogHelper.Action.ServerMessages,
                //        "-", "SendCallBack: " + e.Message);
            }
        }
        public void SetInformLoger(InformationLoger log)
        {
            SocketInformLoger = log;
        }
        private void SocketInformLog(string inform)
        {
            if (SocketInformLoger == null)
            {
                return;
            }
            //string currentTime = System.DateTime.Now.ToString("[yyyy-MM-dd HH:mm:ss]");
            //string localIp, int localPort, int maxListen, 
            SocketInformLoger(inform);
        }

        //private string ProcessData(StateObject so, string receivingData)
        //{
        //    char[] delimiter = { ';', '\n', '\r' };
        //    string replyClient = "";

        //    string[] param = receivingData.Split(delimiter, StringSplitOptions.RemoveEmptyEntries);

        //    so.faKit.BatteryLinkStatus = GetParameterValue("KitStatus", param);

        //    // Check FA Kit and Battery Pack connectivity status: 1 = good; 2 = no connectivity with battery
        //    if (GetParameterValue("KitStatus", param) == "1")
        //    {
        //        // Check Barcode
        //        if (GetParameterValue("Barcode", param) == "DP1234565".ToLower())
        //        {
        //            replyClient = "1";
        //        }
        //        else //Barcode incorrect or not found in list
        //        {
        //            replyClient = "2";
        //        }
        //    }
        //    else        //KitStatus = 2
        //    {
        //        replyClient = "3";
        //    }

        //    return replyClient;
        //}

        //public string GetParameterValue(string parameter, string[] allData)
        //{
        //    string value = "";

        //    foreach (string data in allData)
        //    {
        //        if (data.StartsWith(parameter, true, null) == true)
        //        {
        //            string[] val = data.Split('=');
        //            value = val[1];
        //            break;
        //        }
        //    }

        //    return value;
        //}

        //private bool ServerCloseHandler(CtrlType sig)
        //{
        //    switch (sig)
        //    {
        //        case CtrlType.CTRL_C_EVENT:
        //        case CtrlType.CTRL_SHUTDOWN_EVENT:
        //        case CtrlType.CTRL_CLOSE_EVENT:
        //            {
        //                log.Write("Server is exited");
        //                log.CloseLogger();
        //                string mailbody = "[Warning] RCSS Server has been exited."
        //                    + Environment.NewLine
        //                    + System.DateTime.Now.ToString("[yyyy-MM-dd HH:mm:ss]");
        //                LogHelper.sendingMailBySQL(LogHelper.SendWarningMailAddress, "[Warning] RCSS Server has been exited.", mailbody);
        //                LogHelper.WriteServerDataLog(LogHelper.MsgType.Normal, LogHelper.Action.ServerMessages,
        //                        "-", "Server is exited");
        //                return false;
        //            }
        //        case CtrlType.CTRL_LOGOFF_EVENT:
        //        default:
        //            return false;
        //    }
        //}


    }//public class ServerSocket

    // State object for reading client data asynchronously
    public class StateObject
    {
        // Client  socket.
        public Socket workSocket = null;
        // Size of receive buffer.
        public const int BufferSize = 1024;
        // Receive buffer.
        public byte[] buffer = new byte[BufferSize];
        // Receive buffer for append.
        public List<byte> listBuffer = new List<byte>();
        // Received data string.
        public StringBuilder sb = new StringBuilder();
    }
}//namespace ServerSocketTest
